package com.semidata.trp.business.data;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.ibatis.session.SqlSession;

import com.semidata.rtd.core.http.client.HttpClient;
import com.semidata.rtd.core.http.client.HttpClientErrorHandler;
import com.semidata.rtd.core.http.client.HttpResponseHandler;
import com.semidata.rtd.core.log.Logger;
import com.semidata.rtd.core.service.Service;
import com.semidata.rtd.core.service.ServiceManager;
import com.semidata.trp.business.mapper.ServerMapper;
import com.semidata.trp.business.model.DecisionServerModel;
import com.semidata.trp.business.model.MonitorServerModel;
import com.semidata.trp.business.utils.SessionUtils;
import com.semidata.trp.business.utils.Utils;
import com.semidata.trp.message.business.Business;

public class DataService implements Service {

    private boolean quit = false;
    private Thread thread;
    private final int SLEEP_TIME = 1000;
    private long lastTime = 0;

    private HttpClient client;
    private BusinessData data;
    private AtomicReference<Business.BusinessData> syncData;

    private static class DecisionServer {
        private String name;
        private String businessUrl;
        private String budgetUrl;
        private long lastTime;
        private boolean ok;
        private AtomicBoolean businessSending = new AtomicBoolean(false);
        private AtomicBoolean budgetSending = new AtomicBoolean(false);

        public void setName(String name) {
            this.name = name;
        }

        public void setBusinessUrl(String businessUrl) {
            this.businessUrl = businessUrl;
        }

        public void setBudgetUrl(String budgetUrl) {
            this.budgetUrl = budgetUrl;
        }

        public void setLastTime(long lastTime) {
            this.lastTime = lastTime;
        }

        public void setOk(boolean ok) {
            this.ok = ok;
        }

        public String getName() {
            return name;
        }

        public String getBusinessUrl() {
            return businessUrl;
        }

        public String getBudgetUrl() {
            return budgetUrl;
        }

        public long getLastTime() {
            return lastTime;
        }

        public boolean isOk() {
            return ok;
        }

        public AtomicBoolean getBusinessSending() {
            return businessSending;
        }

        public AtomicBoolean getBudgetSending() {
            return budgetSending;
        }

        public void setBusinessSending(boolean flag) {
            this.businessSending.set(flag);
        }

        public void setBudgetSending(boolean flag) {
            this.businessSending.set(flag);
        }
        
        

    }

    static private class BusinessInfo {
        
        private String businessUrl;
        private AtomicBoolean businessSending;

        public String getBusinessUrl() {
            return businessUrl;
        }

        public void setBusinessUrl(String businessUrl) {
            this.businessUrl = businessUrl;
        }

        public AtomicBoolean getBusinessSending() {
            return businessSending;
        }

        public void setBusinessSending(AtomicBoolean businessSending) {
            this.businessSending = businessSending;
        }

    }

    static private class BudgetInfo {
       
        private String budgetUrl;
        private AtomicBoolean budgetSending;

        public String getBudgetUrl() {
            return budgetUrl;
        }

        public void setBudgetUrl(String budgetUrl) {
            this.budgetUrl = budgetUrl;
        }

        public AtomicBoolean getBudgetSending() {
            return budgetSending;
        }

        public void setBudgetSending(AtomicBoolean budgetSending) {
            this.budgetSending = budgetSending;
        }

    }

    private ReentrantLock serverLock = new ReentrantLock();
    private Map<String, DecisionServer> decisionServers = new HashMap<>();

    @Override
    public void start() {

        data = new BusinessData(new DataLoader());
        syncData = new AtomicReference<>();

        client = new HttpClient();
        client.start();

        thread = new Thread(new Runnable() {

            @Override
            public void run() {
                while (!quit) {
                    updateServers();
                    boolean syncAll = false;
                    if (data.load() || Utils.getDate(System.currentTimeMillis()) != Utils.getDate(lastTime)) {
                        lastTime = System.currentTimeMillis();
                        syncData.set(data.getBusinessData());
                        ServiceManager.getService(BusinessDataDebugService.class).send(syncData.get());
                        syncAll = true;
                    }
                    try {
                        sendBusinessData(syncAll);
                    } catch (Exception e) {
                        // e.printStackTrace();
                    	Logger.log("ERROR", e.getMessage());
                    }
                    try {
                        sendBudgetData();
                    } catch (Exception e) {
                        // e.printStackTrace();
                    	Logger.log("ERROR", e.getMessage());
                    }
                    long time = System.currentTimeMillis() + 1;
                    long sleepTime = SLEEP_TIME - time % SLEEP_TIME;
                    if (sleepTime < SLEEP_TIME / 5)
                        sleepTime += SLEEP_TIME;
                    try {
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        // e.printStackTrace();
                    }
                }
            }

        });

        thread.start();
    }

    @Override
    public void stop() {
        quit = true;

        try {
            thread.join();
        } catch (InterruptedException e) {
            // e.printStackTrace();
        }
        client.stop();
    }

    private void updateServers() {
        serverLock.lock();
        ServerData serverData = new ServerData();

        Map<String, DecisionServer> newMap = new HashMap<>();

        for (DecisionServerModel model : serverData.getDecisionList()) {
            if (!model.isActive())
                continue;
            DecisionServer obj = decisionServers.get(model.getName());
            DecisionServer server = new DecisionServer();
            server.setName(model.getName());
            server.setBusinessUrl(model.getBusinessUrl());
            server.setBudgetUrl(model.getBudgetUrl());
            

            if (obj == null) {
                server.setLastTime(0);
                server.setOk(true);
            } else {
                server.setLastTime(obj.getLastTime());
                server.setOk(obj.isOk());
                server.setBusinessSending(obj.getBusinessSending().get());
                server.setBudgetSending(obj.getBudgetSending().get());
            }
            newMap.put(model.getName(), server);
        }

        decisionServers = newMap;
        serverLock.unlock();
    }

    private void updateServerStatus(String name, boolean ok) {
        serverLock.lock();

        DecisionServer server = decisionServers.get(name);
        if (server != null) {
            server.setOk(ok);
            if (!ok)
                server.setLastTime(0);
        }
        serverLock.unlock();
    }

    private void updateSyncTime(String name, long time) {
        serverLock.lock();

        DecisionServer server = decisionServers.get(name);
        if (server != null) {
            server.setLastTime(time);
        }
        serverLock.unlock();
    }

    private Map<String, BusinessInfo> getBusinessInfo(boolean all) {
        Map<String, BusinessInfo> map = new HashMap<>();
        serverLock.lock();

        for (DecisionServer server : decisionServers.values()) {
            if (!server.isOk())
                continue;

            if (!all && server.getLastTime() >= lastTime)
                continue;

            BusinessInfo info = new BusinessInfo();
            info.setBusinessUrl(server.getBusinessUrl());
            info.setBusinessSending(server.getBusinessSending());
            map.put(server.getName(), info);
        }

        serverLock.unlock();
        return map;
    }

    private Map<String, BudgetInfo> getBudgetInfo() {
        Map<String, BudgetInfo> map = new HashMap<>();
        serverLock.lock();

        for (DecisionServer server : decisionServers.values()) {
            BudgetInfo info = new BudgetInfo();
            info.setBudgetUrl(server.getBudgetUrl());
            info.setBudgetSending(server.getBudgetSending());
            map.put(server.getName(), info);
        }

        serverLock.unlock();
        return map;
    }

    private void sendBusinessData(boolean syncAll) {

        Map<String, BusinessInfo> map = getBusinessInfo(syncAll);

        for (final Entry<String, BusinessInfo> entry : map.entrySet()) {

            final BusinessInfo info = entry.getValue();
            if (!info.getBusinessSending().compareAndSet(false, true)) {
                Logger.log("DEBUG", "is sending business data to server " + entry.getKey() + ", waiting...");
                continue;
            }
            Logger.log("DEBUG", "send business data to server " + entry.getKey());
            try {
                Logger.log("INFO", "send business data to server " + entry.getKey());
                client.send(info.getBusinessUrl(), syncData.get().toByteArray(), null, null, new HttpResponseHandler() {
                    @Override
                    public void handle(HttpResponse response, ByteBuf buffer) {
                        if (response.getStatus().equals(HttpResponseStatus.OK)) {
                            Logger.log("DEBUG", "send business data to " + entry.getKey() + " ok");
                            Logger.log("INFO", "send business data to " + entry.getKey() + " ok");
                            updateSyncTime(entry.getKey(), System.currentTimeMillis());
                        } else {
                            Logger.log("DEBUG", "send business data to " + entry.getKey() + " error: bad response");
                            Logger.log("INFO", "send business data to " + entry.getKey() + " error: bad response");
                            updateServerStatus(entry.getKey(), false);
                        }
                        info.getBusinessSending().set(false);

                    }

                }, new HttpClientErrorHandler() {

                    @Override
                    public void handle(ChannelFuture future) {
                        Logger.log("DEBUG", "business data is received by " + entry.getKey() + ", but error occurs");
                        Logger.log("INFO", "business data is received by " + entry.getKey() + ", but error occurs");
                        updateServerStatus(entry.getKey(), false);
                        info.getBusinessSending().set(false);
                    }

                }, null);
            } catch (Exception e) {
                // e.printStackTrace();
                Logger.log("DEBUG", "send business data error: can not connect to server" + entry.getKey());
                Logger.log("INFO", "send business data error: can not connect to server" + entry.getKey());
                updateServerStatus(entry.getKey(), false);
                info.getBusinessSending().set(false);
            }
        }

    }

    private void sendBudgetData() {

        Map<String, BudgetInfo> map = getBudgetInfo();

        for (final Entry<String, BudgetInfo> entry : map.entrySet()) {

            final BudgetInfo info = entry.getValue();
            if (!info.getBudgetSending().compareAndSet(false, true)) {
                Logger.log("DEBUG", "is sending budget data to server " + entry.getKey() + ", waiting...");
                continue;
            }
            Logger.log("DEBUG", "send budget data to server " + entry.getKey() + " info : " + info.getBudgetUrl());
            try {
                client.send(info.getBudgetUrl(), data.getBudgetData().toByteArray(), null, null, new HttpResponseHandler() {

                    @Override
                    public void handle(HttpResponse response, ByteBuf buffer) {

                        if (response.getStatus().equals(HttpResponseStatus.OK)) {
                            Logger.log("DEBUG", "send budget data to " + entry.getKey() + " ok");
                            updateServerStatus(entry.getKey(), true);
                        } else {
                            Logger.log("DEBUG", "send budget data to " + entry.getKey() + " error: bad response");
                            updateServerStatus(entry.getKey(), false);
                        }
                        info.getBudgetSending().set(false);

                    }

                }, new HttpClientErrorHandler() {

                    @Override
                    public void handle(ChannelFuture future) {

                        Logger.log("DEBUG", "budget data is received by " + entry.getKey() + ", but error occurs");
                        updateServerStatus(entry.getKey(), false);
                        info.getBudgetSending().set(false);

                    }

                }, null);
            } catch (Exception e) {
                Logger.log("DEBUG", "send budget data, error: can not connect to server" + entry.getKey());
                e.printStackTrace();
                updateServerStatus(entry.getKey(), false);
                info.getBudgetSending().set(false);

            }
        }
    }

    public void updateDecisionServer(String name, String businessUrl, String budgetUrl, String reportUrl, boolean active) {
        SqlSession session = SessionUtils.getSession();

        DecisionServerModel model = new DecisionServerModel();
        model.setName(name);
        model.setBusinessUrl(businessUrl);
        model.setBudgetUrl(budgetUrl);
        model.setReportUrl(reportUrl);
        model.setActive(active);

        try {
            ServerMapper mapper = session.getMapper(ServerMapper.class);
            mapper.insertDecisionServer(model);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        } finally {
            SessionUtils.close(session);
        }

    }

    public void updateMonitorServer(String name, boolean active) {
        SqlSession session = SessionUtils.getSession();
        MonitorServerModel model = new MonitorServerModel();
        model.setName(name);
        model.setActive(active);

        try {
            ServerMapper mapper = session.getMapper(ServerMapper.class);
            mapper.insertMonitorServer(model);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
        } finally {
            SessionUtils.close(session);
        }
    }
}
